﻿using Avalon.Core.MVC;
using Avalon.Core.MVC.Attributes;
using Avalon.Core.Resources;
using System;
using System.Linq;
using System.Reflection;
using System.Web.Mvc;

namespace Avalon.Core.Providers {
    /// <summary>
    /// A controller provider
    /// </summary>
    public class ControllerProvider : BaseProvider, IControllerProvider {
        private DefaultControllerFactory _defaultControllerFactory;

        /// <summary>
        /// CTOR
        /// </summary>
        /// <param name="requestContext">
        /// The request context, every provider should contain a request context.
        /// </param>
        public ControllerProvider() {
            _defaultControllerFactory = new DefaultControllerFactory();
            if (string.IsNullOrWhiteSpace(Controller)) throw new InvalidOperationException(AvalonResources.ControllerProvider_ControllerUnknown);
        }

        public IControllerDescriptor CreateController() {
            //Create the default controller. Keep the default MVC implementation alive.
            IControllerDescriptor controllerDescriptor = createDefaultController();
            if (controllerDescriptor != null) return controllerDescriptor;

            //An avalon controller is a controller which uses the Avalon framework to execute pipelines, invoke actions and 
            //return rendered views to the browser.
            controllerDescriptor = createAvalonController();
            if (controllerDescriptor != null) return controllerDescriptor;

            //throw new exception when the controller is still null
            throw new NullReferenceException("The controller cannot be null.");
        }

        private IControllerDescriptor createDefaultController() {
            try {
                //set the renderer indication.
                ContextProvider.RendererType = RendererType.DefaultRenderer;

                //Get the controller via the 'normal' MVC way. This controller will pass through the normal pipelines as defined by MVC.
                IController controller = _defaultControllerFactory.CreateController(_requestContext, Controller);
                if (controller == null) return null;

                //Because of the extensible nature of the Avalon framework it is possible that the controller exists but the requested action 
                //doesn't. this next code searches for the action.
                MethodInfo actionMethodInfo = controller.GetType().GetMethods().FirstOrDefault(m => m.Name == Action);
                if (actionMethodInfo == null) return null;

                //The last thing we need to check is if the action implements the RendererAttribute. If the action implements a RendererAttribute
                //we define a custom renderer and should process this request differently. There are two ways to implement a renderer, the first
                //is to create a Typed Renderer in which the renderer type is a type implementing IViewRenderer. The second way is to specify a
                //view name and a folder name. The application will search this folder for the view and return this view.
                RendererAttribute rendererAttribute = actionMethodInfo.GetCustomAttribute<RendererAttribute>(true);
                if (rendererAttribute != null) {
                    ContextProvider.RendererType = RendererType.TypedRenderer;
                    //A renderer can be defined by a type, this type should implement IViewRenderer
                    if (rendererAttribute.RendererType != null) {
                        return new ControllerDescriptor() {
                            Controller = controller,
                            RendererDescriptor = ProviderContainer.Current.RendererProvider.GetViewRenderer(rendererAttribute.RendererType),
                            EntityDescriptor = ProviderContainer.Current.EntityProvider.GetEntityDescriptor()
                        };
                    }
                    //A renderer can also be defined by a string with the view name and a string for the foldername. These views are 
                    //maintained in the file system of the application
                    else if (!string.IsNullOrWhiteSpace(rendererAttribute.ViewName) && !string.IsNullOrWhiteSpace(rendererAttribute.FolderName)) {
                        ContextProvider.RendererType = RendererType.ViewRenderer;
                        return new ControllerDescriptor() {
                            Controller = controller,
                            RendererDescriptor = ProviderContainer.Current.RendererProvider.GetViewRenderer(rendererAttribute.ViewName, rendererAttribute.FolderName),
                            EntityDescriptor = ProviderContainer.Current.EntityProvider.GetEntityDescriptor()
                        };
                    }
                }

                //return the controlller
                return new ControllerDescriptor() { Controller = controller };
            }
            catch {
                return null;
            }
        }

        private IControllerDescriptor createAvalonController() {
            //Because the Avalon controller factory is heavely realiant on the type of the entity and the name of the controller we Initialize 
            //the entity provider. Here we select the right entity descriptor and this descriptor is passed to the controller factory.
            IEntityProvider provider = ProviderContainer.Current.EntityProvider;
            IEntityDescriptor descriptor = provider.GetEntityDescriptor();

            //Create a generic controller without the view model.
            if (descriptor != null) {
                var baseGenericControllerType = typeof(BaseGenericEntityController<>).MakeGenericType(descriptor.EntityType);
                IController controller = (IController)Activator.CreateInstance(baseGenericControllerType);
                ContextProvider.RendererType = RendererType.GeneratedRenderer;
                return new ControllerDescriptor() { 
                    Controller = controller, 
                    RendererDescriptor = ProviderContainer.Current.RendererProvider.GetViewRenderer(),
                    EntityDescriptor = ProviderContainer.Current.EntityProvider.GetEntityDescriptor()
                };
            }

            return null;
        }

        /// <summary>
        /// Get the controller name from an entity type.
        /// </summary>
        /// <param name="entityType">The type of the entity for which you want to return the controller.</param>
        /// <returns>the controller name</returns>
        public static string GetControllerName(Type entityType) {
            Type eType = System.Data.Objects.ObjectContext.GetObjectType(entityType);
            return GetControllerName(eType.Name);
        }

        /// <summary>
        /// Get the controller name from an entity type.
        /// </summary>
        /// <param name="entityName">The name of the entity for which you want to return the controller.</param>
        /// <returns>the controller name</returns>
        public static string GetControllerName(string entityName) {
            IEntityProvider provider = ProviderContainer.Current.EntityProvider;
            IEntityDescriptor descriptor = provider.GetEntityDescriptor(entityName);
            if (descriptor != null) {
                return descriptor.ControllerName;
            }
            else {
                return string.Empty;
            }
        }

    }
}
